#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/10/12 09:35
# @Author  : 王凯
# @File    : pdd_account_pool.py
# @Project : scrapy_spider
import json
import os
import random
import time
from datetime import datetime
from enum import Enum, unique
from typing import Optional, List

import requests

from utils.db.redisdb import RedisDB
from utils.redis_lock import RedisLock
from utils.tools import timestamp_to_date
from utils.user_pool.base_user_pool import UserPoolInterface, NormalUser, GoldUser
from loguru import logger as log


def send_msg(msg, level="DEBUG", message_prefix=""):
    url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=28fb0f70-321e-461a-92c2-88c61921753f"
    data = {
        "msgtype": "text",
        "text": {"content": msg + '\n' + message_prefix},
    }

    headers = {"Content-Type": "application/json"}

    try:
        response = requests.post(
            url, headers=headers, data=json.dumps(data).encode("utf8")
        )
        result = response.json()
        response.close()
        if result.get("errcode") == 0:
            return True
        else:
            raise Exception(result.get("errmsg"))
    except Exception as e:
        log.error("报警发送失败。 报警内容 {}, error: {}".format(msg, e))
        return False


class PDDGoldUser(GoldUser):
    """
    昂贵的账号
    """

    def __init__(self, max_use_times, **kwargs):
        super().__init__(max_use_times, **kwargs)
        self._proxy_dict = None


def __init__(self, **kwargs):
    super().__init__(**kwargs)
    self.cookies = kwargs.get("cookies")
    self.login_time = kwargs.get("login_time")
    self.login_status = kwargs.get("login_status")
    self.last_use_time = kwargs.get("last_use_time")
    self.used_times = kwargs.get("used_times")
    self.used_for_spider_name = kwargs.get("used_for_spider_name")


@unique
class GoldUserStatus(Enum):
    # 使用状态
    USED = "used"
    SUCCESS = "success"
    OVERDUE = "overdue"  # cookie 过期
    SLEEP = "sleep"
    EXCEPTION = "exception"
    # 登陆状态
    LOGIN_SUCCESS = "login_success"
    LOGIN_FALIED = "login_failed"


class PddUserPool(UserPoolInterface):
    """
    账号昂贵、限制查询次数的用户的UserPool
    """

    def __init__(
            self,
            redis_key,
            *,
            users: List[PDDGoldUser],
            keep_alive=False,
    ):
        """
        @param redis_key: user存放在redis中的key前缀
        @param users: 账号信息
        @param keep_alive: 是否保持常驻，以便user不足时立即补充
        """
        self._tab_user_pool = "pdd:account:h5:{redis_key}:h_{user_type}_pool".format(
            redis_key=redis_key, user_type="gold"
        )

        self.users = users
        self._keep_alive = keep_alive

        self._redisdb = RedisDB()
        self._users_id = []

        if not users:
            raise ValueError("not users")

        # 给user的类属性复制
        self.users[0].__class__.redisdb = self._redisdb
        self.users[0].__class__.redis_key = self._tab_user_pool

        self.__sync_users_base_info()
        self.__sycn_users_info()

    def __sync_users_base_info(self):
        # 本地同步基本信息到redis, 注 只能在初始化函数内同步
        for user in self.users:
            cache_user = self.get_user_by_id(user.user_id)
            if cache_user:
                for key, value in user.to_dict().items():
                    if not key.startswith("_"):
                        setattr(cache_user, key, value)
                cache_user.sycn_to_redis()

    def __sycn_users_info(self):
        # redis同步登录信息到本地
        for index, user in enumerate(self.users):
            cache_user = self.get_user_by_id(user.user_id)
            if cache_user:
                self.users[index] = cache_user

    def _load_users_id(self):
        self._users_id = self._redisdb.hkeys(self._tab_user_pool)
        if self._users_id:
            random.shuffle(self._users_id)

    def _get_user_id(self):
        if not self._users_id:
            self._load_users_id()

        if self._users_id:
            return self._users_id.pop()

    def get_user_by_id(self, user_id: str) -> PDDGoldUser:
        user_str = self._redisdb.hget(self._tab_user_pool, user_id)
        if user_str:
            user = PDDGoldUser(**eval(user_str))
            return user

    def get_user(
            self,
            block=True,
            username=None,
            used_for_spider_name=None,
            not_limit_use_interval=False,
    ) -> Optional[PDDGoldUser]:
        """
        @params username: 获取指定的用户
        @params used_for_spider_name: 独享式使用，独享爬虫的名字。其他爬虫不可抢占
        @params block: 无用户时是否等待
        @params not_limit_frequence: 不限制使用频率
        @return: PDDGoldUser
        """
        while True:
            try:
                user_id = username or self._get_user_id()
                user_str = None
                if user_id:
                    user_str = self._redisdb.hget(self._tab_user_pool, user_id)

                if (not user_id or not user_str) and block:
                    self._keep_alive = False
                    self.run(username)
                    continue

                # 取到用户
                user = PDDGoldUser(**eval(user_str))

                # 独占式使用，若为其他爬虫，检查等待使用时间是否超过独占时间，若超过则可以使用
                if (
                        user.get_used_for_spider_name()
                        and user.get_used_for_spider_name() != used_for_spider_name
                ):
                    wait_time = time.time() - user.get_last_use_time()
                    if wait_time < user.exclusive_time:
                        log.info(
                            "用户{} 被 {} 爬虫独占，需等待 {} 秒后才可使用".format(
                                user.username,
                                user.get_used_for_spider_name(),
                                user.exclusive_time - wait_time,
                            )
                        )
                        time.sleep(1)
                        continue

                if not user.is_overwork() and user.is_at_work_time():
                    if not user.cookies:
                        log.debug(f"用户 {user.username} 未登录，尝试登录")
                        self._keep_alive = False
                        self.run(username)
                        continue

                    if not_limit_use_interval or user.is_time_to_use():
                        user.set_used_for_spider_name(used_for_spider_name)
                        log.debug("使用用户 {}".format(user.username))
                        self.record_user_status(user.user_id, GoldUserStatus.USED)
                        return user
                    else:
                        log.debug("{} 用户使用间隔过短 查看下一个用户".format(user.username))
                        time.sleep(1)
                        continue
                else:
                    if not user.is_at_work_time():
                        log.info("用户 {} 不在工作时间 sleep 60s".format(user.username))
                        if block:
                            time.sleep(60)
                            continue
                        else:
                            return None

            except Exception as e:
                log.exception(e)
                time.sleep(1)

    def del_user(self, user_id: str):
        user = self.get_user_by_id(user_id)
        if user:
            user.set_cookies(None)
            self.record_user_status(user.user_id, GoldUserStatus.OVERDUE)

    def add_user(self, user: PDDGoldUser):
        user.sycn_to_redis()

    def delay_use(self, user_id: str, delay_seconds: int):
        user = self.get_user_by_id(user_id)
        if user:
            user.set_delay_use(delay_seconds)

        self.record_user_status(user_id, GoldUserStatus.SLEEP)

    def record_success_user(self, user_id: str):
        self.record_user_status(user_id, GoldUserStatus.SUCCESS)

    def record_exception_user(self, user_id: str):
        self.record_user_status(user_id, GoldUserStatus.EXCEPTION)

    def run(self, username=None):
        while True:
            try:
                with RedisLock(
                        key=self._tab_user_pool, lock_timeout=3600, wait_timeout=0
                ) as _lock:
                    if _lock.locked:
                        self.__sycn_users_info()
                        online_user = 0
                        for user in self.users:
                            if username and username != user.username:
                                continue

                            try:
                                if user.cookies:
                                    online_user += 1
                                    continue

                                # 预检查
                                if not user.is_time_to_login():
                                    log.info(
                                        "账号{}与上次登录时间间隔过短，暂不登录: 将在{}登录使用".format(
                                            user.username, user.next_login_time()
                                        )
                                    )
                                    continue

                                user = self.login(user)
                                if user.cookies:
                                    # 保存cookie
                                    user.set_login_time()
                                    self.add_user(user)
                                    self.record_user_status(
                                        user.user_id, GoldUserStatus.LOGIN_SUCCESS
                                    )
                                    log.debug("登录成功 {}".format(user.username))
                                    online_user += 1
                                else:
                                    log.info("登录失败 {}".format(user.username))
                                    self.record_user_status(
                                        user.user_id, GoldUserStatus.LOGIN_FALIED
                                    )
                            except NotImplementedError:
                                log.error(
                                    f"{self.__class__.__name__} must be implementation login method！"
                                )
                                os._exit(0)
                            except Exception as e:
                                log.exception(e)
                                msg = f"{user.username} 账号登陆失败 exception: {str(e)}"
                                log.info(msg)
                                self.record_user_status(
                                    user.user_id, GoldUserStatus.LOGIN_FALIED
                                )

                                send_msg(
                                    msg=msg,
                                    level="error",
                                    message_prefix=f"{user.username} 账号登陆失败",
                                )

                        log.info("当前在线user数为 {}".format(online_user))

                if self._keep_alive:
                    time.sleep(10)
                else:
                    break

            except Exception as e:
                log.exception(e)
                time.sleep(1)

    def record_user_status(self, user_id: str, status: GoldUserStatus):
        # metrics.emit_counter(user_id, 1, classify=f"users_{status.value}")
        pass

    def login(self, user: PDDGoldUser) -> PDDGoldUser:
        """
        登录 生产cookie
        """
        if user.username == '9091223058827':
            user.cookies = {
                "api_uid": "CkppEWcJC4wKOQCZzUqsAg==",
                "_nano_fp": "Xpmxl0EYn59qX0d8XT_CoOvf6lXu~RrsyDSj67Uj",
                "webp": "1",
                "PDDAccessToken": "SPESHUX3MMQ5JKC5JXHGRV4XL4KOUUHZNAJS72BGXU6KYDTDAQPA1218b0b",
                "pdd_user_id": "9091223058827",
                "pdd_user_uin": "25UHR3DLOCMPANQENSUHZ4VHZA_GEXDA",
                "pdd_vds": "gaLLNOPQImIyIaPILItoPoonLinnniIQOILEPtyGtbnEyanEPaLnLPENaPnG"
            }
        return user


if __name__ == '__main__':
    pool = PddUserPool(redis_key="pdd_user_pool", users=[
        PDDGoldUser(
            150,
            use_interval=0,
            work_time=(7, 23),
            login_interval=30 * 60,
            exclusive_time=None,
            username="9091223058827",
            password="9091223058827",
            proxy_dict={
                "host": '221.226.54.119',
                "port": "32384",
                "username": "squid",
                "password": "70226ff9ff818edbd816e8c06b76ef97",
            },
        ),
        PDDGoldUser(
            150,
            use_interval=0,
            work_time=(7, 23),
            login_interval=30 * 60,
            exclusive_time=None,
            username="9091223058827",
            password="9091223058827",
            proxy_dict={
                "host": '221.226.54.119',
                "port": "32384",
                "username": "squid",
                "password": "70226ff9ff818edbd816e8c06b76ef97",
            },
        )
    ], keep_alive=False)
    print(pool.get_user())

    PDDGoldUser(
        150,
        username='32384',
        password='32384',
        cookies={
            "api_uid": "CiSJamcKDbC+rABWNiMFAg==",
            "_nano_fp": "Xpmxl0maX5gaX5dqnT_WRkyCriW6eBFqrLvBdTOu",
            "webp": "1",
            "jrpl": "tbHcQlV9trVnYzuFIn6bvyA4qBfVGaW2",
            "njrpl": "tbHcQlV9trVnYzuFIn6bvyA4qBfVGaW2",
            "dilx": "C1U2cmHBqqLgn5I_uruU1",
            "PDDAccessToken": "V5GAL5USGAA52YUEJSSEX3TQUMB6GMFOBURH24IYVVNLUR6J33HQ1203177",
            "pdd_user_id": "5936276929585",
            "pdd_user_uin": "ZNUMKXRMPFO6OLO46TJ6GFLR4U_GEXDA",
            "pdd_vds": "gaRgAXpZkvFVKppWAKFKFVFMRWMXJXjXVXRvRKkHpHSgjFpXKZMFSpFXpgVv"
        },
        proxy_dict={
            "host": '221.225.182.151',
            "port": "32384",
            "username": "squid",
            "password": "70226ff9ff818edbd816e8c06b76ef97",
        },
        user_agent='Mozilla/5.0 (Linux; Android 14; PHP110 Build/UKQ1.230924.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.188 Mobile Safari/537.36 XWEB/1260117 MMWEBSDK/20240501 MMWEBID/1594 MicroMessenger/8.0.50.2701(0x2800323E) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64'
    )

    PDDGoldUser(
        150,
        username='32384',
        password='32384',
        cookies={
            "api_uid": "CkgwqWcKDScCUQBy01MiAg==",
            "_nano_fp": "Xpmxl0maXpU8l0TJXo_H~2NMkvlEEJ8m~N6wkA_d",
            "webp": "1",
            "jrpl": "9AymJ5N0jo9lj3hy1GgK5cj6dtZ68YF9",
            "njrpl": "9AymJ5N0jo9lj3hy1GgK5cj6dtZ68YF9",
            "dilx": "HsBuqYR~Q3awXgsXJGq9s",
            "PDDAccessToken": "C7LWSXPVMCSPLE2P2DBFBOHNVDVQPB3C6XJU3BSLS3E4CG22BFCQ122ea07",
            "pdd_user_id": "3458129512170",
            "pdd_user_uin": "U3FOYCWOOJPXEJNWFEIE4ZVRZY_GEXDA",
            "pdd_vds": "gadLxOEQniNmEaGyyotNlLtysyOGNOINmbyoOnItdLxOsEyLEbsGnnyndmOI"
        },
        proxy_dict={
            "host": '49.85.189.43',
            "port": "32384",
            "username": "squid",
            "password": "70226ff9ff818edbd816e8c06b76ef97",
        },
        user_agent='Mozilla/5.0 (Linux; Android 14; PHP110 Build/UKQ1.230924.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.188 Mobile Safari/537.36 XWEB/1260117 MMWEBSDK/20240501 MMWEBID/1594 MicroMessenger/8.0.50.2701(0x2800323E) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64'
    )

    PDDGoldUser(
        150,
        username='32384',
        password='32384',
        cookies={
            "api_uid": "CkppEWcJC4wKOQCZzUqsAg==",
            "_nano_fp": "Xpmxl0EYn59qX0d8XT_CoOvf6lXu~RrsyDSj67Uj",
            "webp": "1",
            "PDDAccessToken": "SPESHUX3MMQ5JKC5JXHGRV4XL4KOUUHZNAJS72BGXU6KYDTDAQPA1218b0b",
            "pdd_user_id": "9091223058827",
            "pdd_user_uin": "25UHR3DLOCMPANQENSUHZ4VHZA_GEXDA",
            "pdd_vds": "gaLLNOPQImIyIaPILItoPoonLinnniIQOILEPtyGtbnEyanEPaLnLPENaPnG"
        },
        proxy_dict={
            "host": '221.226.54.119',
            "port": "32384",
            "username": "squid",
            "password": "70226ff9ff818edbd816e8c06b76ef97",
        },
        user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 NetType/WIFI MicroMessenger/7.0.20.1781(0x6700143B) WindowsWechat(0x63090c11) XWEB/11275 Flue'
    )
